; sidemu.asm
; sidemu - converted to ca65 by A.M. 

;****************************************************************************
;*****
;*****
;*****  Sid Vicious
;*****
;*****  SID sound chip emulation for VIC 20
;*****
;*****  aleksi.eeben@gmail.com
;*****
;*****
;****************************************************************************

	.export entry
	.export SIDIMAGE
	.exportzp zpuser
	.export initadr
	.export execadr
	.export song 

	SIDIMAGE = $03e0
	zpuser = reserved1

.segment "ZEROPAGE"

;-----  noise generator

noise:     	.res 32

;-----  envelopes

.struct Osci
	es		.byte	; envelope state (off/att/dec/rel)
	elev	.byte	; current level
	eadd	.byte	; add value
	ediv	.byte	; time divider
	ecnt	.byte	; time count
	sus		.byte	; sustain level
	prevg	.byte	; state of gate bit previous frame
.endstruct

envelopes:	.repeat 3
				.tag Osci
			.endrep

o1es = envelopes + Osci::es
o1elev = envelopes + Osci::elev						
o1eadd = envelopes + Osci::eadd						
o1ediv = envelopes + Osci::ediv						
o1ecnt = envelopes + Osci::ecnt						
o1sus = envelopes + Osci::sus						
o1prevg = envelopes + Osci::prevg						

;-----  misc

tmp:		.res 1
frq:		.res 1						; used for frequency conversion
frqh:		.res 1


reserved1:	.res $18					; .ORG    $0050

;-----  NMI routine for zeropage

NMI:
        sta     <(storea_+1)          		; store a & x
        stx     <(storex_+1)


;-----  voice 1

o1acc_:
        lda     #$00                    ; o1acc
        clc
o1add_:
        adc     #$00                    ; o1add
        sta     o1acc_+1
o1acch_:
        lda     #$00                    ; o1acch
o1addh_:
        adc     #$00                    ; o1addh
        sta     o1acch_+1

        and     #$1f


;-      oscillator switch

o1wav_:
        jmp     o1off


;-      pulse

o1pul:
o1pw_:
        cmp     #$10                    ; pulse width
        bcc     o1phe_
o1ple_:
        lda     #$10
        bpl     o1out
o1off:
o1phe_:
        lda     #$10
        bpl     o1out


;-      noise

o1noi:
        sta     <(o1nsp_+1)
o1nsp_:
        ldx     $00                     ; get value from noise table
        bpl     o1lev_


;-      triangle

o1tri:
        asl
        cmp     #$20
        bcc     o1env
        eor     #$3f
        ;

;-      sawtooth
o1saw:
        ;

;-      envelope level (for all waveforms except pulse wave)

o1env:
        tax
o1lev_:
        lda     $02e0,x                 ; output value from multiply table


;-      output to mix

o1out:
        sta     <(o1mix_+1)


;-----  voice 2

o2acc_:
        lda     #$00                    ; o2acc
        clc
o2add_:
        adc     #$00                    ; o2add
        sta     o2acc_+1
o2acch_:
        lda     #$00                    ; o2acch
o2addh_:
        adc     #$00                    ; o2addh
        sta     o2acch_+1

        and     #$1f


;-      oscillator switch

o2wav_:
        jmp     o2off


;-      pulse

o2pul:
o2pw_:
        cmp     #$10                    ; pulse width
        bcc     o2phe_
o2ple_:
        lda     #$10
        bpl     o2out
o2off:
o2phe_:
        lda     #$10
        bpl     o2out


;-      noise

o2noi:
        sta     <(o2nsp_+1)
o2nsp_:
        ldx     $00                     ; get value from noise table
        bpl     o2lev_


;-      triangle

o2tri:
        asl
        cmp     #$20
        bcc     o2env
        eor     #$3f
        ;

;-      sawtooth

o2saw:
        ;

;-      envelope level (for all waveforms except pulse wave)

o2env:
        tax
o2lev_:
        lda     $02e0,x                 ; output value from multiply table


;-      output to mix

o2out:
        sta     <(o2mix_+1)


;-----  voice 3

o3acc_:
        lda     #$00                    ; o3acc
        clc
o3add_:
        adc     #$00                    ; o3add
        sta     o3acc_+1
o3acch_:
        lda     #$00                    ; o3acch
o3addh_:
        adc     #$00                    ; o3addh
        sta     o3acch_+1

        and     #$1f


;-      oscillator switch

o3wav_:
        jmp     o3off


;-      pulse

o3pul:
o3pw_:
        cmp     #$10                    ; pulse width
        bcc     o3phe_
o3ple_:
        lda     #$10
        bpl     o3out
o3off:
o3phe_:
        lda     #$10
        bpl     o3out


;-      noise

o3noi:
        sta     <(o3nsp_+1)
o3nsp_:
        ldx     $00                     ; get value from noise table
        bpl     o3lev_


;-      triangle

o3tri:
        asl
        cmp     #$20
        bcc     o3env
        eor     #$3f
        ;

;-      sawtooth

o3saw:
        ;

;-      envelope level (for all waveforms except pulse wave)

o3env:
        tax
o3lev_:
        lda     $02e0,x                 ; output value from multiply table


;-      output and mix voices

o3out:
o1mix_:
        adc     #$00                    ; o1 + o3 in x
        tax
o2mix_:
        lda     $0100,x                 ; o2 in address, read from cliptable
excol:	ora		#0
        sta     $900e                   ; play sample


;-      return from NMI

storea_:
        lda     #$00                    ; restore a & x
storex_:
        ldx     #$00

        bit     $9114                   ; NMI clear
        rti

;-      to set envelope level (0-15)
;       - write (envlev / 2) << 5 to o1env_+1
;       - write 16 + envlev to o1phe_+1
;       - write 16 - envlev to o1ple_+1

;-      to set waveform write jump switch to o1wav_+1
;-      to mute oscillator, write $10 to o1phe_+1 and set o1wav_+1 to o1off

;-      to set pulse width (highest 5 bits) write to o1pw_+1

;-      to set frequency write to o1add_+1 and o1addh_+1
;       - internal frequency value is SID freq. / 16 + SID freq. / 64
;       - with noise waveform internal frequency is SID frequency / 256

;-      internal envelope trigging rules
;       - trig envelope attack if gate bit switched to 1 from last frame 0
;       - trig envelope attack if waveform is $01 or $09 (for players which
;         write to sid registers multiple times during the same frame)
;       - go to release phase if gate switched to 0 from last frame 1


	.segment "STACK"

;-----  mixing cliptable

        .INCBIN "cliptab.bin"

;-----  envelope tables

attackrate:
        .byte 8,7,6,5,4,3,3,2,2,1,1,1,1,1,1,1
attackdiv:
        .byte 1,1,1,1,1,1,1,1,1,1,3,5,6,19,31,50

decayrate:
        .byte -8,-5,-3,-2,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
decaydiv:
        .byte 1,1,1,1,1,1,1,2,2,3,5,8,12,26,60,100      ; FIXME!

;       .DB     1,1,1,1,1,2,2,3,3,5,9,15,19,57,94,150
;       this table is based on sid info but the decays sound too long


;-----  table of selfmod locations

selfmodloc:
        .byte o1add_,o1addh_,o1wav_,o1pw_,o1ple_,o1phe_,o1lev_
        .byte o2add_,o2addh_,o2wav_,o2pw_,o2ple_,o2phe_,o2lev_
        .byte o3add_,o3addh_,o3wav_,o3pw_,o3ple_,o3phe_,o3lev_


;-----  table of wavetypes per channel

wavetypes:
        .byte o1off,o1tri,o1saw,o1pul,o1noi,0,0
        .byte o2off,o2tri,o2saw,o2pul,o2noi,0,0
        .byte o3off,o3tri,o3saw,o3pul,o3noi,0,0

		.res $38

	.segment "THIRD"
	
;-----  volume multiply table

        .INCBIN "voltab.bin"

;-----  music data

	.segment "MODULE"

	init_sid = *
	exec_sid = init_sid+3	


	.segment "CODE"	

;-----  entry 

entry:  sei

		lda		$912e
		ora		#$80
		sta		enirq
		
        lda     #$7f                    ; stop all IRQs
        sta     $912e

        lda     #$7f                    ; stop all NMIs
        sta     $911e
		

		lda 	$900e					; save extra color for graphics 
		and 	#$F0
		sta		excol+1

song = *+1 
        lda     #$00                    ; init music
initadr = *+1
        jsr     init_sid

        lda     #<NMI
        sta     $0318
        lda     #>NMI
        sta     $0319
        sta     $9115

        lda     #$b1                    ; SID $0400 = Sid Vicious $0050
        sta     $9114

        lda     #<IRQ
        sta     $0314
        lda     #>IRQ
        sta     $0315

        lda     #$56                    ; frame rate 50 Hz
        sta     $9125
        lda     #$86
        sta     $9124

        lda     #$c0                    ; start nmi timer 1 interrupt
        sta     $911e

		sei

enirq = *+1 
        lda     #$c0                    ; restart irq interrupts
        sta     $912e

        cli


;-----  generate chaos when idle

rndloop:
        ldx     #$1f
_rnd:
        lda     $00,x
        adc     $9128
        and     #$1f
        sta     $00,x
        dex
        bpl     _rnd

        jmp     rndloop


;-----  frame irq

IRQ:
        ldy     #$0e                    ; voices $0e, $07, $00


nextvoice:

;-      check gate bit change

checkgate:
        lda  	SIDIMAGE+4,y            ; read waveform register
        sta     tmp

        ; interpret wave $09/$01 as gate off? maybe good for some players?
        ; yes, should do that

        lsr                             ; check gate bit
        ldx     #$00
        bcs     _gateon

_gateoff:
        lda		envelopes+6,y           ; check if gate on previous frame
        beq     _gateok                 ; no it was off, no action

        lda		SIDIMAGE+6,y            ; get release value
        and     #$0f
        tax
        lda		decayrate,x             ; attack rate
        sta     o1eadd,y
        lda		decaydiv,x              ; attack frame time divider
        sta     o1ediv,y
        lda     #$03
        sta     o1es,y                  ; set envelope phase to release
        lda     #$01
        sta     o1ecnt,y                ; reset envelope time divider count
        lda     #$00
        sta     o1sus,y                 ; (internal) sustain level to zero
        tax                             ; gate is now off 'previous' frame
        beq     _gateok

_gateon:
        lda     envelopes+6,y           ; check if gate off previous frame
        bne     _gatekeep

        lda     SIDIMAGE+5,y            ; get attack value
        lsr
        lsr
        lsr
        lsr
        tax
        lda     attackrate,x            ; attack rate
        sta     o1eadd,y
        lda     attackdiv,x             ; attack frame time divider
        sta     o1ediv,y
        lda     #$01
        sta     o1es,y                  ; set envelope phase to attack
        sta     o1ecnt,y                ; reset envelope time divider count
        lda     #$00
        sta     o1elev,y                ; start from zero level

        ldx     #$01
_gateok:
        stx     envelopes+6,y           ; update o1prevg
_gatekeep:

        jsr     process_envelopes


;-      check & set waveform

        lda     tmp
        asl
        bcc     _cwnonoise


;-      set noise waveform

_setnoise:
        lda     wavetypes+4,y           ; write o1noi
        ldx     selfmodloc+2,y          ; to o1wav+1
        sta     $01,x

        lda     SIDIMAGE+1,y            ; use sid high freq value for noise
        ldx     selfmodloc+0,y          ; O1ADD
        sta     $01,x

        lda     #$00
        ldx     selfmodloc+1,y          ; zero O1ADDH for noise
        sta     $01,x
        jmp     _waveok

_cwnonoise:
        asl                             ; check waveform bits
        bcs     _setpulse

        asl
        bcs     _setsawtooth

        asl
        bcs     _settriangle


;-      mute voice

_setmute:
        lda     #$10                    ; zero O1PHE for muted voice
        ldx     selfmodloc+5,y
        sta     $01,x

        lda     wavetypes+0,y           ; write o1off
        ldx     selfmodloc+2,y          ; to o1wav+1
        sta     $01,x
        bne     _waveok
        

;-      set triangle

_settriangle:
        lda     wavetypes+1,y           ; write o1tri
        ldx     selfmodloc+2,y          ; to o1wav+1
        sta     $01,x
        bne     conv_freq


;-      set sawtooth

_setsawtooth:
        lda     wavetypes+2,y           ; write o1saw
        ldx     selfmodloc+2,y          ; to o1wav+1
        sta     $01,x
        bne     conv_freq


;-      set pulse waveform & pulse width

_setpulse:
        lda     wavetypes+3,y           ; write o1pul
        ldx     selfmodloc+2,y          ; to o1wav+1
        sta     $01,x

        lda     SIDIMAGE+3,y            ; 5 high bits of pulse width
        sta     tmp
        lda     SIDIMAGE+2,y
        asl
        rol     tmp
        lda     tmp

        ldx     selfmodloc+3,y          ; O1PW
        sta     $01,x

        lda     #$10                    ; set level of pulse high (o1phe)
        clc
        adc     o1elev,y
        adc     o1elev,y
        ldx     selfmodloc+4,y
        sta     $01,x

        lda     #$10                    ; set level of pulse low (o1ple)
        sec
        sbc     o1elev,y
        sbc     o1elev,y
        ldx     selfmodloc+5,y
        sta     $01,x


;-      convert frequency

conv_freq:
        lda     SIDIMAGE+1,y            ; sid frequency / 16
        sta     tmp
        lda     SIDIMAGE,y
        lsr     tmp
        ror
        lsr     tmp
        ror
        lsr     tmp
        ror
        lsr     tmp
        ror

        sta     frq
        lda     tmp
        sta     frqh

        lda     frq                     ; + sid frequency / 64
        lsr     tmp
        ror
        lsr     tmp
        ror

        clc     
        adc     frq

        ldx     selfmodloc+0,y          ; O1ADD
        sta     $01,x

        lda     tmp
        adc     frqh

        ldx     selfmodloc+1,y          ; O1ADDH
        sta     $01,x

_waveok:

;-      set envelope level

        lda     o1elev,y                ; write envelope level << 5
        asl
        asl
        asl
        asl
        asl
        ldx     selfmodloc+6,y          ; to o1lev+1
        sta     $01,x


;-      next voice

        tya
        sec
        sbc     #$07
        tay
        bmi     _voicesok
        jmp     nextvoice

_voicesok:

;-----  call music frame routine and leave IRQ
execadr = *+1
        jsr     exec_sid
        jmp     $eb15


;-----  process envelopes

process_envelopes:
        tya
        tax

        lda     o1es,x
        beq     _envdone

        dec     o1ecnt,x                ; time countdown
        bne     _envdone

        ldy     o1ediv,x                ; reset time countdown
        sty     o1ecnt,x

        cmp     #$01
        beq     _envattack


;-      decay or release phase

_envdecay:
        lda     o1eadd,x                ; volume down
        clc
        adc     o1elev,x
        bmi     _envstop                ; check o1es
                                        ; decay = stay at sus
                                        ; release = voice stopped

        cmp     o1sus,x                 ; check if above sustain level
        bcs     _envok

_envsuslev:
        lda     o1sus,x                 ; stay at sustain level
        sta     o1elev,x
        bpl     _envok


;-      attack phase

_envattack:
        lda     o1eadd,x                ; volume up
        clc
        adc     o1elev,x
        cmp     #$07
        bcc     _envok

        inc     o1es,x                  ; move to decay phase

        lda     SIDIMAGE+5,x            ; get decay value
        and     #$0f
        tay
        lda     decayrate,y             ; attack rate
        sta     o1eadd,x
        lda     decaydiv,y              ; attack frame time divider
        sta     o1ediv,x
        lda     #$01
        sta     o1ecnt,x                ; reset envelope time divider count

        lda     SIDIMAGE+6,x            ; get sustain value
        lsr
        lsr
        lsr
        lsr
        lsr
        sta     o1sus,x                 ; sustain level 0-7

        lda     #$07
_envok:
        sta     o1elev,x

_envdone:
        txa
        tay

        rts

_envstop:
        lda     o1es,x
        cmp     #$02
        beq     _envsuslev

        lda     #$00
        sta     o1es,x
        sta     o1elev,x
        beq     _envdone
